#pragma once

#include <vector>
#include <string>
#include <memory>
#include <thread>
#include "../util/allocator.hh"
#include "../thirdparty/mysql/interface/rdb.h"
#include "../timer_queue.hh"
#include "../spsc_queue.hpp"
#include "../singleton.hpp"

namespace krdb {
    class MysqlRow;
    class RdbRow;
    class MysqlConnection;
}

namespace kratos { namespace mysql {

class MySQLConn;

using StringVector = std::vector<std::string>;
using SizeVector = std::vector<std::size_t>;

// MySQL row data
class MySQLResultRow {
    StringVector fields_; // row's field data

public:
    // ctor
    // @param row krdb::MysqlRow instance
    // @param fieldCount row's field count
    MySQLResultRow(krdb::MysqlRow* row,  std::size_t fieldCount);
    // dtor
    ~MySQLResultRow();
    // returns The row's data seperated by field
    const StringVector& getFields() const;
};

using FieldNames = std::vector<std::string>;
using Rows = std::vector<MySQLResultRow*>;

// The result of MySQL command
class MySQLResult {
    FieldNames fields_; // field name vector
    Rows rows_; // row of resultset
    std::string error_; // error string of MySQL for the command
    int errorID_; // error ID of MySQL for the command
    std::uint32_t sequence_; // command sequence ID
    std::uint32_t taskID_; // user defined task ID
    std::uint64_t userData_; // user defined data
    std::string command_; // MySQL command

public:
    // ctor
    // @param sequence command sequence ID
    // @param taskID user defined task ID
    // @param userData user defined data
    // @param command MySQL command
    MySQLResult(std::uint32_t sequence, std::uint32_t taskID, std::uint64_t userData, std::string& command);
    // dtor
    ~MySQLResult();
    // returns error string of MySQL for the command
    const std::string& getErrorString() const;
    // returns error ID of MySQL for the command
    int getErrorID() const;
    // returns user defined task ID
    std::uint32_t getTaskID();
    // returns user data
    std::uint64_t getUserData();
    // verification
    operator bool();
    // returns the vector of field name
    const FieldNames& getFields() const;
    // returns row set of command result
    const Rows& getRows() const;

private:
    // set MySQL error string of command
    void setErrorString(const std::string& error);
    // set MySQL error ID of command
    void setErrorID(int errorID);
    // assigns field names
    void addFields(char** name, std::size_t fieldCount);
    // add a row data
    // @param row krdb::RdbRow instance
    // @param fieldCount field count of row
    void addRow(krdb::RdbRow* row, std::size_t fieldCount);
    // returns command sequence ID
    std::uint32_t getSequence();
    // returns MySQL command
    const std::string& getCommand() const;

    friend class MySQLConn;
};

// MySQL client connection
class MySQLConn {
    std::string host_; // The host of mysqld
    int port_; // The port of mysqld
    std::string user_; // The user name of mysqld
    std::string password_; // The password for user of mysqld
    std::string database_; // The database name of mysqld
    krdb::MysqlConnection* conn_; // connection instance
    std::unique_ptr<std::thread> routine_; // thread routine
    corelib::TimerQueue timerQueue_; // timer queue
    using MySQLCallback = std::function<void(MySQLResult&)>;
    using MySQLTimeoutCallback = std::function<void(const std::string&, std::uint32_t, std::uint64_t)>;
    // Task information of each MySQL command
    struct TaskInfo {
        std::string command; // MySQL command
        std::uint32_t taskID; // user defined task ID
        std::uint64_t userData; // user defined data
        corelib::TimerID timerID; // timer ID
        MySQLCallback cb; // MySQL result callback
        MySQLTimeoutCallback tocb; // callback for command timeout
    };
    using TaskMap = std::unordered_map<std::uint32_t, TaskInfo>;
    using TimerMap = std::unordered_map<corelib::TimerID, std::uint32_t>;
    TaskMap taskMap_; // {sequence ID, TaskInfo}
    TimerMap timerMap_; // {timer ID, sequence ID}
    corelib::SPSCQueue<MySQLResult*> input_; // The queue of waiting MySQL command
    corelib::SPSCQueue<MySQLResult*> output_; // The queue of finished MySQL result
    bool run_; // running flag

public:
    // ctor
    // @param host The host of mysqld
    // @param port The port of mysqld
    // @param user The user name of mysqld
    // @param password The password for user of mysqld
    // @param database The database name of mysqld
    MySQLConn(const std::string& host, int port = 3306, const std::string& user = "root", const std::string& password = "",
        const std::string& database = "");
    // dtor
    ~MySQLConn();
    // The main cycle of connection
    void update(std::time_t now);
    // startup
    // @retval true
    // @retval false fail
    bool start();
    // cleanup
    void stop();
    // push a MySQL command to worker thread
    // @param SQLCommand MySQL command string
    // @param taskID user defined task ID
    // @param userData user defined data to distinguish different source with same taskID
    // @param cb The callback of finished task
    // @param tocb The callback of timeout task
    // @param timeout task timeout in millionseconds
    // @retval true
    // @retval false fail
    bool doCommand(const std::string& SQLCommand, std::uint32_t taskID = 0, std::uint64_t userData = 0, MySQLCallback cb = nullptr,
        MySQLTimeoutCallback tocb = nullptr, std::time_t timeout = 0);
    // to std::string
    operator std::string();
};

// MySQL connection query policy, reserved
enum class MySQLHostFindPolicy : std::uint32_t {
    MYSQL_HOST_FIND_RANDOM = 1,
    MYSQL_HOST_FIND_ROUNDROBIN,
};

// MySQL client pool
class MySQL {
    using ConnVector = std::vector<MySQLConn*>;
    using ConnMap = std::unordered_map<std::string, ConnVector>;
    ConnMap connMap_; // {name, ConnVector}

public:
    // ctor
    MySQL();
    // dtor
    ~MySQL();
    // cleanup
    void stop();
    // add a new host and spawn thread pool for it
    // @param name connection name
    // @param host The host of mysqld
    // @param port The port of mysqld
    // @param user The user name of mysqld
    // @param password The password for user of mysqld
    // @param database The database name of mysqld
    // @param maxPoolSize The maximum size of connection pool
    // @retval true
    // @retval false fail
    bool addHost(const std::string& name, const std::string& host, int port = 3306, const std::string& user = "root",
        const std::string& password = "", const std::string& database = "", std::size_t maxPoolSize = 0);
    // find a connection
    // @param name name given via addHost
    // @param policy reserved
    // @return MySQL connection
    MySQLConn* findConn(const std::string& name, MySQLHostFindPolicy policy = MySQLHostFindPolicy::MYSQL_HOST_FIND_RANDOM);
    // main cycle
    void update(std::time_t now);
};

#define MySQLRef (*kratos::corelib::Singleton<kratos::mysql::MySQL>::instance())

}}
